package com.michael.retail.gateway.config;

import cn.hutool.core.collection.CollectionUtil;
import com.michael.retail.commons.constant.AuthConstant;
import com.michael.retail.commons.pojo.enums.OAuth2ClientEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.Collection;

/**
 * 网关鉴权管理器 - 判断是否有对应权限
 *
 * @author Michael
 */
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ResourcesServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private final RedisTemplate<String, Object> redisTemplate;

    @Override
    public Mono<AuthorizationDecision> check(
            Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext
    ) {

        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        HttpMethod method = request.getMethod();

        // 预检请求放行
        if (method == HttpMethod.OPTIONS) {
            return Mono.just(new AuthorizationDecision(true));
        }
        String path = request.getURI().getPath();

        //ServerWebExchange exchange = authorizationContext.getExchange();
        // 【声明定义】Ant路径匹配模式，“请求路径”和缓存中权限规则的“URL权限标识”匹配
        //PathMatcher pathMatcher = new AntPathMatcher();

        String token = request.getHeaders().getFirst(AuthConstant.AUTHORIZATION_KEY);

        assert method != null;
        log.info("请求来了：{}\t{}\t{}", method.name(), path, token);

        return authenticationMono.map(authentication -> {
            return new AuthorizationDecision(hasPermission(authentication, method, path));
        }).defaultIfEmpty(new AuthorizationDecision(false));
    }

    // 是否有权限
    public boolean hasPermission(Authentication authentication, HttpMethod method, String path) {

        if (!(authentication instanceof AnonymousAuthenticationToken)) {
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            if (CollectionUtil.isEmpty(authorities)) {
                log.warn("没有任何权限：{}", authentication.getPrincipal());
                return false;
            }
            // token 所属 clientId
            String clientId = ((Jwt) authentication.getPrincipal()).getClaims()
                                                                    .get(AuthConstant.CLIENT_ID_KEY).toString();
            OAuth2ClientEnum clientEnum = OAuth2ClientEnum.getByClientId(clientId);

            if (clientEnum != null) {
                switch (clientEnum) {
                    case ADMIN:
                        return checkAdmin(authorities, path);
                    case TEST:
                    case MINWX:
                }
            }
        }

        return false;
    }

    private boolean checkAdmin(Collection<? extends GrantedAuthority> authorities, String requestPath) {

        //从Redis中获取当前路径可访问角色列表
        Object required = redisTemplate.opsForHash().get(AuthConstant.AUTHORIZATION_REDIS_KEY_ADMIN, requestPath);
        if (required != null) {
            //String required = obj.toString();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(required)) {
                    return true;
                }
            }
        }
        return false;
    }

}
